package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Component;
import com.lagou.edu.annotation.Repository;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.utils.ClassUtil;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Bean工厂，仅负责生产bean，依赖关系的管理由 @Autowired 注解处理
 * <p>
 * Created by Ju·Felix on 2020/5/3.
 */
public class BeanFactoryByAnno {

    private static final String pkgNamePrefix = "com.lagou.edu";
    private static final List<Class<? extends Annotation>> COMPONENT_LIST = Arrays.asList(Component.class, Repository.class, Service.class);

    private static final ConcurrentHashMap<String, Object> beanPool = new ConcurrentHashMap<>();

    static {
        // 通过反射获取包下的所有类
        List<Class<?>> classes = null;
        try {
            classes = ClassUtil.getClasses(pkgNamePrefix);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 过滤生产bean并放入bean池
        if (Objects.nonNull(classes)) {
            classes.stream()
                    // 过滤出带 @Component/@Repository/@Service 注解的类
                    .filter(BeanFactoryByAnno::componentAnnoAndAlias)
                    // 注册实例到 bean池 中
                    .forEach(clazz -> {
                        String beanId = convert2BeanId(clazz);
                        Object clazzInstance = null;
                        try {
                            // 反射实例化bean
                            clazzInstance = clazz.newInstance();
                        } catch (InstantiationException | IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        if (Objects.nonNull(clazzInstance)) {
                            beanPool.put(beanId, clazzInstance);
                        }
                    });
        }

        // 依赖关系管理：获取每个bean中的所有变量，尝试注入操作
        // 提取出所有bean实例
        beanPool.values()
                // 对bean实例进行尝试注入操作
                .forEach(BeanFactoryByAnno::tryInject);
    }

    public static <T> T getBean(String beanId) {
        return (T) beanPool.get(beanId);
    }

    /**
     * 根据注解中的 value 值生成对应的 beanId
     * - 如果value非空，则使用value指定的值作为key
     * - 如果value为空，则默认使用首字母小写的类名作为key
     *
     * @param clazz
     * @return
     */
    private static String convert2BeanId(Class<?> clazz) {
        String annoValue = getComponentAnnoValue(clazz);
        if (StringUtils.isEmpty(annoValue)) {
            String clazzName = clazz.getSimpleName();
            return convert2BeanId(clazzName);
        }
        return annoValue;
    }

    private static String convert2BeanId(String s) {
        if (!Character.isLowerCase(s.charAt(0))) {
            s = StringUtils.join(Character.toLowerCase(s.charAt(0)), s.substring(1));
        }
        return s;
    }

    /**
     * 尝试注入被 @Autowired 注解的实例变量
     *
     * @param obj
     */
    private static void tryInject(Object obj) {
        Class<?> clazz = obj.getClass();
        Arrays.stream(clazz.getDeclaredFields())
                .filter(field -> Objects.nonNull(field.getAnnotation(Autowired.class)))
                .forEach(field -> {
                    // 获取变量名称，即变量名称必须和 @Service 指定的 beanId 一致
                    String beanId = field.getName();
                    // 从 bean池 中获取对应bean实例
                    Object beanInstance = beanPool.get(beanId);
                    // 暴力设置
                    field.setAccessible(true);
                    try {
                        field.set(obj, beanInstance);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                });
    }

    /**
     * 简单实现是否含有 @Component 及其注解别名判断
     *
     * @param clazz
     * @return
     */
    private static boolean componentAnnoAndAlias(Class<?> clazz) {
        for (Class<? extends Annotation> annoClazz : COMPONENT_LIST) {
            if (Objects.nonNull(clazz.getAnnotation(annoClazz))) {
                return true;
            }
        }
        return false;
    }

    private static String getComponentAnnoValue(Class<?> clazz) {
        Component component = clazz.getAnnotation(Component.class);
        if (Objects.nonNull(component)) {
            return component.value();
        }
        Repository repository = clazz.getAnnotation(Repository.class);
        if (Objects.nonNull(repository)) {
            return repository.value();
        }
        Service service = clazz.getAnnotation(Service.class);
        if (Objects.nonNull(service)) {
            return service.value();
        }

        return null;
    }
}
